package top.z.template;

import top.z.template.parse.Parser;
import top.z.template.parse.Span;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 加载模板
 */
public interface Loader {
    /**
     * 从路径加载模板
     */
    Template load(String path);

    /**
     * 其他 {@link Loader} 实现的基类, 用于缓存模板并通过 include 语句递归加载模板引用的其他模板
     */
    abstract class CacheLoader implements Loader {

        Map<String, Template> cache = new ConcurrentHashMap<>();

        abstract Source loadSource(String path);

        /**
         * 从路径加载模板
         */
        public Template load(String path) {
            if (cache.containsKey(path)) {
                return cache.get(path);
            }
            Source   source   = this.loadSource(path);
            Template template = this.compile(source);
            cache.put(path, template);
            return template;
        }

        public Template compile(Source source) {
            // 解析模板
            Parser.Result result = new Parser().parse(source);
            // 解析 include 和 macro
            String rootDir;
            if (new File(source.getPath()).getParent() != null) {
                rootDir = new File(source.getPath()).getParent() + "/";
            } else {
                rootDir = "";
            }
            return new Template(result.getNode());
        }

    }

    /**
     * 从目录加载模板
     */
    class FileLoader extends CacheLoader {
        @Override
        protected Source loadSource(String path) {
            try {
                return new Source(path, StreamUtil.read(Files.newInputStream(Path.of(path))));
            } catch (Throwable t) {
                Error.error("Couldn't load template '" + path + "'.", new Span(new Source(path, " "), 0, 0), t);
                throw new RuntimeException("");
            }
        }
    }

    class StreamUtil {
        public static String read(InputStream in) throws IOException {
            byte[]                buffer = new byte[1024 * 10];
            ByteArrayOutputStream out    = new ByteArrayOutputStream();
            int                   read;
            while ((read = in.read(buffer)) != -1) {
                out.write(buffer, 0, read);
            }
            return out.toString(StandardCharsets.UTF_8);
        }
    }
}
